/*
 * @(#)LinkFactory.java 1.7 04/04/25
 * 
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. SUN
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.tools.doclets.internal.toolkit.util.links;

import com.sun.javadoc.*;

/**
 * A factory that constructs links from given link information.
 * 
 * @author Jamie Ho
 * @since 1.5
 */
public abstract class LinkFactory {
	
	/**
	 * Return an empty instance of the link output object.
	 * 
	 * @return an empty instance of the link output object.
	 */
	protected abstract LinkOutput getOutputInstance();
	
	/**
	 * Constructs a link from the given link information.
	 * 
	 * @param linkInfo the information about the link.
	 * @return the output of the link.
	 */
	public LinkOutput getLinkOutput(LinkInfo linkInfo) {
		if (linkInfo.type != null) {
			Type type = linkInfo.type;
			LinkOutput linkOutput = getOutputInstance();
			if (type.isPrimitive()) {
				// Just a primitive.
				linkInfo.displayLength += type.typeName().length();
				linkOutput.append(type.typeName());
			} else if (type.asWildcardType() != null) {
				// Wildcard type.
				linkInfo.isTypeBound = true;
				linkInfo.displayLength += 1;
				linkOutput.append("?");
				WildcardType wildcardType = type.asWildcardType();
				Type[] extendsBounds = wildcardType.extendsBounds();
				for (int i = 0; i < extendsBounds.length; i++) {
					linkInfo.displayLength += i > 0 ? 2 : 9;
					linkOutput.append(i > 0 ? ", " : " extends ");
					setBoundsLinkInfo(linkInfo, extendsBounds[i]);
					linkOutput.append(getLinkOutput(linkInfo));
				}
				Type[] superBounds = wildcardType.superBounds();
				for (int i = 0; i < superBounds.length; i++) {
					linkInfo.displayLength += i > 0 ? 2 : 7;
					linkOutput.append(i > 0 ? ", " : " super ");
					setBoundsLinkInfo(linkInfo, superBounds[i]);
					linkOutput.append(getLinkOutput(linkInfo));
				}
			} else if (type.asTypeVariable() != null) {
				linkInfo.isTypeBound = true;
				// A type variable.
				Doc owner = type.asTypeVariable().owner();
				if ((!linkInfo.excludeTypeParameterLinks)
						&& owner instanceof ClassDoc) {
					linkInfo.classDoc = (ClassDoc) owner;
					linkInfo.label = type.typeName();
					linkOutput.append(getClassLink((LinkInfo) linkInfo));
				} else {
					// No need to link method type parameters.
					linkInfo.displayLength += type.typeName().length();
					linkOutput.append(type.typeName());
				}
				
				Type[] bounds = type.asTypeVariable().bounds();
				if (!linkInfo.excludeTypeBounds) {
					linkInfo.excludeTypeBounds = true;
					for (int i = 0; i < bounds.length; i++) {
						linkInfo.displayLength += i > 0 ? 2 : 9;
						linkOutput.append(i > 0 ? " & " : " extends ");
						setBoundsLinkInfo(linkInfo, bounds[i]);
						linkOutput.append(getLinkOutput(linkInfo));
					}
				}
			} else if (type.asClassDoc() != null) {
				// A class type.
				if (linkInfo.isTypeBound && linkInfo.excludeTypeBoundsLinks) {
					// Since we are excluding type parameter links, we should
					// not
					// be linking to the type bound.
					linkInfo.displayLength += type.typeName().length();
					linkOutput.append(type.typeName());
					linkOutput.append(getTypeParameterLinks(linkInfo));
					return linkOutput;
				} else {
					linkInfo.classDoc = type.asClassDoc();
					linkOutput = getClassLink((LinkInfo) linkInfo);
					if (linkInfo.includeTypeAsSepLink) {
						linkOutput.append(getTypeParameterLinks(linkInfo, false));
					}
				}
			}
			
			if (linkInfo.isVarArg) {
				if (type.dimension().length() > 2) {
					// Javadoc returns var args as array.
					// Strip out the first [] from the var arg.
					linkInfo.displayLength += type.dimension().length() - 2;
					linkOutput.append(type.dimension().substring(2));
				}
				linkInfo.displayLength += 3;
				linkOutput.append("...");
			} else {
				linkInfo.displayLength += type.dimension().length();
				linkOutput.append(type.dimension());
			}
			return linkOutput;
		} else if (linkInfo.classDoc != null) {
			// Just a class link
			LinkOutput linkOutput = getClassLink((LinkInfo) linkInfo);
			if (linkInfo.includeTypeAsSepLink) {
				linkOutput.append(getTypeParameterLinks(linkInfo, false));
			}
			return linkOutput;
		} else {
			return null;
		}
	}
	
	private void setBoundsLinkInfo(LinkInfo linkInfo, Type bound) {
		linkInfo.classDoc = null;
		linkInfo.label = null;
		linkInfo.type = bound;
	}
	
	/**
	 * Return the link to the given class.
	 * 
	 * @param linkInfo the information about the link to construct.
	 * 
	 * @return the link for the given class.
	 */
	protected abstract LinkOutput getClassLink(LinkInfo linkInfo);
	
	/**
	 * Return the link to the given type parameter.
	 * 
	 * @param linkInfo the information about the link to construct.
	 * @param typeParam the type parameter to link to.
	 */
	protected abstract LinkOutput getTypeParameterLink(LinkInfo linkInfo,
			Type typeParam);
	
	/**
	 * Return the links to the type parameters.
	 * 
	 * @param linkInfo the information about the link to construct.
	 * @return the links to the type parameters.
	 */
	public LinkOutput getTypeParameterLinks(LinkInfo linkInfo) {
		return getTypeParameterLinks(linkInfo, true);
	}
	
	/**
	 * Return the links to the type parameters.
	 * 
	 * @param linkInfo the information about the link to construct.
	 * @param isClassLabel true if this is a class label. False if it is the
	 *        type parameters portion of the link.
	 * @return the links to the type parameters.
	 */
	public LinkOutput getTypeParameterLinks(LinkInfo linkInfo,
			boolean isClassLabel) {
		LinkOutput output = getOutputInstance();
		Type[] vars;
		if (linkInfo.executableMemberDoc != null) {
			vars = linkInfo.executableMemberDoc.typeParameters();
		} else if (linkInfo.type != null
				&& linkInfo.type.asParameterizedType() != null) {
			vars = linkInfo.type.asParameterizedType().typeArguments();
		} else if (linkInfo.classDoc != null) {
			vars = linkInfo.classDoc.typeParameters();
		} else {
			// Nothing to document.
			return output;
		}
		if (((linkInfo.includeTypeInClassLinkLabel && isClassLabel) || (linkInfo.includeTypeAsSepLink && !isClassLabel))
				&& vars.length > 0) {
			linkInfo.displayLength += 1;
			output.append(getLessThanString());
			for (int i = 0; i < vars.length; i++) {
				if (i > 0) {
					linkInfo.displayLength += 1;
					output.append(",");
				}
				output.append(getTypeParameterLink(linkInfo, vars[i]));
			}
			linkInfo.displayLength += 1;
			output.append(getGreaterThanString());
		}
		return output;
	}
	
	/**
	 * Return &amp;lt;, which is used in type parameters. Override this if your
	 * doclet uses something different.
	 * 
	 * @return return &amp;lt;, which is used in type parameters.
	 */
	protected String getLessThanString() {
		return "&lt;";
	}
	
	/**
	 * Return &amp;gt;, which is used in type parameters. Override this if your
	 * doclet uses something different.
	 * 
	 * @return return &amp;gt;, which is used in type parameters.
	 */
	protected String getGreaterThanString() {
		return "&gt;";
	}
}
